from fastapi import APIRouter, UploadFile, File, HTTPException, Depends, Query, Security
from app.utils.minio_client import get_minio_client
from app.database.database import get_db
from app.services.user_service import get_default_user_id
from app.services.file_service import calculate_file_hash, find_duplicate_file, save_file_info
from sqlalchemy.orm import Session
import os
import logging
import tempfile
from datetime import datetime
from app.services.auth_service import get_current_user
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
from app.database.models import User
from app.utils.response_utils import ResponseResult
from app.core.config import settings
from app.schemas.file_schema import FileVerifyRequest, FileVerifyResponse, BatchFileVerifyResponse, BatchFileVerifyResult
from app.utils.bloom_filter import file_hash_filter
from typing import List
import time

# 配置日志记录器
logger = logging.getLogger(__name__)

# 获取 MinIO 客户端实例
minio_client = get_minio_client()

router = APIRouter(
    prefix="/api", 
    tags=["文件上传"],
    responses={
        401: {"description": "未认证"},
        403: {"description": "无权限访问"},
        404: {"description": "资源不存在"},
        500: {"description": "服务器内部错误"}
    }
)

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="/auth/login",
    scheme_name="OAuth2PasswordBearer",
    auto_error=True
)

async def get_current_user_from_token(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
) -> User:
    """从token中获取当前用户"""
    return get_current_user(db, token)

def determine_file_type(content_type, filename):
    """根据文件MIME类型和扩展名确定文件类型分类
    
    Args:
        content_type: 文件的MIME类型
        filename: 文件名
    
    Returns:
        str: 文件类型分类（photo/video/audio/document/other）
    """
    # 获取文件扩展名（小写）
    ext = os.path.splitext(filename)[1].lower()
    logger.debug(f"检测文件类型: filename={filename}, extension={ext}, content_type={content_type}")
    
    # 优先根据扩展名判断常见图片类型（即使MIME类型错误也能识别）
    if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.tiff']:
        logger.debug(f"根据扩展名识别为图片: {ext}")
        return 'photo'
    
    # 优先根据扩展名判断常见视频类型
    if ext in ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv', '.webm']:
        logger.debug(f"根据扩展名识别为视频: {ext}")
        return 'video'
    
    # 优先根据扩展名判断常见音频类型
    if ext in ['.mp3', '.wav', '.ogg', '.m4a', '.flac', '.aac']:
        logger.debug(f"根据扩展名识别为音频: {ext}")
        return 'audio'
    
    # 优先根据扩展名判断常见文档类型
    if ext in ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.csv', '.rtf']:
        logger.debug(f"根据扩展名识别为文档: {ext}")
        return 'document'
    
    # 如果扩展名不明确，再根据MIME类型判断
    # 图片类型
    if content_type.startswith('image/'):
        return 'photo'
    
    # 视频类型
    elif content_type.startswith('video/'):
        return 'video'
    
    # 音频类型
    elif content_type.startswith('audio/'):
        return 'audio'
    
    # 文档类型
    elif content_type.startswith(('application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument', 'text/')):
        return 'document'
    
    # 其他类型
    else:
        logger.debug(f"未知文件类型: content_type={content_type}, extension={ext}")
        return 'other'

@router.post("/upload",
    summary="上传文件",
    description="上传文件到服务器，自动检测文件类型、计算哈希值和提取元数据。支持图片、视频、音频、文档等多种类型文件，自动检测重复文件。",
    tags=["文件上传"]
)
async def upload_file(
    file: UploadFile = File(...),
    directory_id: int = Query(0, description="目标目录ID，0表示根目录"),
    file_hash: str = Query(None, description="文件哈希值(SHA256)，如果提供则不再计算哈希"),
    current_user: User = Depends(get_current_user_from_token),
    db: Session = Depends(get_db)
):
    """上传文件
    
    支持多种文件类型，自动进行文件类型判断、哈希计算和元数据提取。
    自动检测重复文件，避免存储相同内容的多个副本。
    
    参数:
        - **file**: 要上传的文件
        - **directory_id**: 目标目录ID，0表示根目录
        - **file_hash**: 文件哈希值(SHA256)，如果提供则不再计算哈希
        - **current_user**: 当前登录用户（通过Token获取）
    
    返回:
        - **file_id**: 文件ID
        - **filename**: 存储的文件名
        - **original_filename**: 原始文件名
        - **storage_path**: 存储路径
        - **size**: 文件大小(字节)
        - **content_type**: 文件MIME类型
        - **file_type**: 文件类型分类(photo/video/audio/document/other)
        - **create_time**: 文件创建时间
        - **hash**: 文件哈希值
        - **is_duplicate**: 是否为重复文件
    """
    # 创建临时文件
    temp_file = tempfile.NamedTemporaryFile(delete=False)
    temp_file_path = temp_file.name
    try:
        logger.debug(f"开始处理文件上传: {file.filename}, directory_id={directory_id}")
        logger.debug(f"文件MIME类型: {file.content_type}")
        
        # 获取用户ID（如果未提供，则使用默认用户）
        user_id = current_user.id
        
        logger.debug(f"使用用户ID: {user_id}")
        
        # 将上传的文件内容写入临时文件
        content = await file.read()
        temp_file.write(content)
        temp_file.flush()
        
        # 重要：关闭文件句柄，防止Windows上的文件占用问题
        temp_file.close()
        
        # 获取文件大小
        file_size = len(content)
        logger.debug(f"文件大小: {file_size} 字节")
        
        # 如果提供了哈希值就使用它，否则计算哈希值
        if file_hash:
            logger.debug(f"使用前端提供的文件哈希值: {file_hash}")
        else:
            # 计算文件哈希值
            try:
                file_hash = calculate_file_hash(content)
                logger.debug(f"计算得到文件哈希值: {file_hash}")
            except Exception as e:
                logger.error(f"计算哈希值失败: {str(e)}")
                # 如果无法计算哈希值，尝试从文件路径计算
                try:
                    # 确保临时文件已写入并关闭
                    temp_file.close()
                    file_hash = calculate_file_hash(temp_file.name)
                    logger.debug(f"从临时文件计算得到哈希值: {file_hash}")
                except Exception as inner_e:
                    logger.error(f"从临时文件计算哈希值也失败: {str(inner_e)}")
                    raise HTTPException(status_code=500, detail=f"无法计算文件哈希值: {str(e)}, {str(inner_e)}")
        
        # 检查是否是重复文件
        duplicate = find_duplicate_file(db, user_id, file_hash)
        if duplicate:
            logger.debug(f"文件已存在，返回现有文件信息")
            result = {
                "file_id": duplicate.id,
                "file_path": duplicate.file_path,
                "original_filename": duplicate.file_name,
                "size": duplicate.file_size,
                "upload_time": duplicate.upload_time,
                "create_time": duplicate.create_time,
                "is_duplicate": True
            }
            return ResponseResult.success(data=result, message="文件已存在")
        
        # 确定文件类型和扩展名
        file_type = determine_file_type(file.content_type, file.filename)
        logger.debug(f"文件类型: {file_type}")
        
        # 保存文件信息到数据库
        file_info = save_file_info(
            db=db,
            user_id=user_id,
            original_filename=file.filename,
            temp_file_path=temp_file_path,
            file_type=file_type,
            file_size=file_size,
            file_hash=file_hash,
            mime_type=file.content_type,
            extension=os.path.splitext(file.filename)[1].lower(),
            directory_id=directory_id
        )
        
        result = {
            "file_id": file_info.id,
            "filename": os.path.basename(file_info.file_path),
            "original_filename": file.filename,
            "storage_path": file_info.file_path,
            "size": file_size,
            "content_type": file.content_type,
            "file_type": file_type,
            "create_time": file_info.create_time.isoformat(),
            "hash": file_hash,
            "is_duplicate": False
        }
        
        return ResponseResult.success(data=result, message="文件上传成功")
    except Exception as e:
        logger.error(f"文件上传失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"文件上传失败: {str(e)}")
    finally:
        # 确保临时文件存在且文件句柄已关闭后再删除
        try:
            # 确保文件已关闭
            if not temp_file.closed:
                temp_file.close()
                
            # 然后尝试删除文件
            if os.path.exists(temp_file_path):
                # 添加短暂延迟，确保文件句柄完全释放（Windows系统上可能需要）
                time.sleep(0.1)
                os.unlink(temp_file_path)
                logger.debug(f"临时文件已删除: {temp_file_path}")
        except Exception as e:
            logger.warning(f"删除临时文件失败: {str(e)}")

@router.post("/verify",
    summary="验证文件哈希",
    description="验证文件哈希值是否已存在，用于前端上传前检查。支持单个哈希验证和批量哈希验证。通过布隆过滤器快速判断文件是否已存在。",
    tags=["文件上传"],
    response_model=ResponseResult
)
async def verify_file_hash(
    request: FileVerifyRequest,
    current_user: User = Depends(get_current_user_from_token),
    db: Session = Depends(get_db)
):
    """验证文件哈希值是否已存在
    
    前端在上传文件前先计算哈希值并发送到此接口验证，服务端快速判断文件是否已存在。
    如文件已存在，则可以直接引用现有文件而不需要重新上传。
    支持单个哈希验证和批量哈希验证。
    
    参数:
        - **file_hash**: 单个文件哈希值(SHA256)
        - **file_hashes**: 批量文件哈希值列表(SHA256)
    
    返回:
        - 单个验证时:
            - **exists**: 文件是否已存在
            - **file_id**: 如存在，文件的ID
            - **upload_permission**: 是否允许上传
            - **message**: 提示信息
        - 批量验证时:
            - **results**: 验证结果列表
            - **total**: 总验证数量
            - **exists_count**: 已存在文件数量
    """
    logger.debug(f"验证文件哈希，用户ID: {current_user.id}")
    
    # 判断优先使用批量验证还是单个验证
    if request.file_hashes is not None and len(request.file_hashes) > 0:
        # 有批量哈希参数，优先使用批量验证
        return verify_multiple_hashes(db, current_user.id, request.file_hashes)
    elif request.file_hash is not None:
        # 没有批量参数但有单个哈希参数，使用单个验证
        return verify_single_hash(db, current_user.id, request.file_hash)
    else:
        # 参数错误
        return ResponseResult.failed(message="未提供有效的哈希值参数", code=400)

def verify_single_hash(db: Session, user_id: int, file_hash: str):
    """验证单个文件哈希
    
    Args:
        db: 数据库会话
        user_id: 用户ID
        file_hash: 文件哈希值
        
    Returns:
        ResponseResult: 验证结果
    """
    logger.debug(f"验证单个文件哈希: {file_hash}, user_id={user_id}")
    
    # 无论布隆过滤器结果如何，都直接查询当前用户的文件
    # 布隆过滤器只是用来优化性能，不影响业务逻辑
    from app.database.models import File
    
    # 只查询当前用户自己上传的文件（用户隔离）
    user_file = db.query(File).filter(
        File.user_id == user_id,
        File.hash == file_hash,
        File.is_deleted == False
    ).first()
    
    if user_file:
        logger.debug(f"找到用户文件: ID={user_file.id}")
        return ResponseResult.success(
            data={
                "exists": True,
                "file_id": user_file.id,
                "upload_permission": False,
                "message": "文件已存在，无需重新上传"
            },
            message="文件已存在"
        )
    
    # 如果没有找到用户自己的文件，则表示该用户可以上传
    return ResponseResult.success(
        data={
            "exists": False,
            "file_id": None,
            "upload_permission": True,
            "message": "文件不存在，可以上传"
        },
        message="可以上传文件"
    )

def verify_multiple_hashes(db: Session, user_id: int, file_hashes: List[str]):
    """批量验证文件哈希
    
    Args:
        db: 数据库会话
        user_id: 用户ID
        file_hashes: 文件哈希值列表
        
    Returns:
        ResponseResult: 批量验证结果
    """
    logger.debug(f"批量验证文件哈希，数量: {len(file_hashes)}")
    
    results = []
    exists_count = 0
    
    # 导入File模型
    from app.database.models import File
    
    # 优化：一次性查询所有哈希值对应的文件记录
    valid_hashes = [h for h in file_hashes if h]  # 过滤掉空值
    user_files = {}
    
    if valid_hashes:
        # 查询当前用户所有匹配哈希的文件（批量查询提高性能）
        files = db.query(File).filter(
            File.user_id == user_id,
            File.hash.in_(valid_hashes),
            File.is_deleted == False
        ).all()
        
        # 构建哈希到文件的映射，方便后续查找
        for file in files:
            user_files[file.hash] = file
    
    for file_hash in file_hashes:
        # 跳过空哈希
        if not file_hash:
            continue
        
        # 查找用户自己的文件（从之前批量查询的结果中获取）
        user_file = user_files.get(file_hash)
        
        if user_file:
            # 找到用户自己的文件
            exists_count += 1
            results.append(BatchFileVerifyResult(
                file_hash=file_hash,
                exists=True,
                file_id=user_file.id,
                upload_permission=False
            ))
        else:
            # 用户没有上传过该文件
            results.append(BatchFileVerifyResult(
                file_hash=file_hash,
                exists=False,
                file_id=None,
                upload_permission=True
            ))
    
    # 返回批量验证结果
    return ResponseResult.success(
        data={
            "results": results,
            "total": len(results),
            "exists_count": exists_count
        },
        message=f"完成批量哈希验证，共{len(results)}个，已存在{exists_count}个"
    )